{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "# Transformations, Eigenvectors, and Eigenvalues\n",
    "\n",
    "Matrices and vectors are used together to manipulate spatial dimensions. This has a lot of applications, including the mathematical generation of 3D computer graphics, geometric modeling, and the training and optimization of machine learning algorithms. We're not going to cover the subject exhaustively here; but we'll focus on a few key concepts that are useful to know when you plan to work with machine learning.\n",
    "\n",
    "## Linear Transformations\n",
    "You can manipulate a vector by multiplying it with a matrix. The matrix acts a function that operates on an input vector to produce a vector output. Specifically, matrix multiplications of vectors are *linear transformations* that transform the input vector into the output vector.\n",
    "\n",
    "For example, consider this matrix ***A*** and vector ***v***:\n",
    "\n",
    "$$ A = \\begin{bmatrix}2 & 3\\\\5 & 2\\end{bmatrix} \\;\\;\\;\\; \\vec{v} = \\begin{bmatrix}1\\\\2\\end{bmatrix}$$\n",
    "\n",
    "We can define a transformation ***T*** like this:\n",
    "\n",
    "$$ T(\\vec{v}) = A\\vec{v} $$\n",
    "\n",
    "To perform this transformation, we simply calculate the dot product by applying the *RC* rule; multiplying each row of the matrix by the single column of the vector:\n",
    "\n",
    "$$\\begin{bmatrix}2 & 3\\\\5 & 2\\end{bmatrix} \\cdot  \\begin{bmatrix}1\\\\2\\end{bmatrix} = \\begin{bmatrix}8\\\\9\\end{bmatrix}$$\n",
    "\n",
    "Here's the calculation in Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "v = np.array([1,2])\n",
    "A = np.array([[2,3],\n",
    "              [5,2]])\n",
    "\n",
    "t = A@v\n",
    "print (t)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this case, both the input vector and the output vector have 2 components - in other words, the transformation takes a 2-dimensional vector and produces a new 2-dimensional vector; which we can indicate like this:\n",
    "\n",
    "$$ T: \\rm I\\!R^{2} \\to \\rm I\\!R^{2} $$\n",
    "\n",
    "Note that the output vector may have a different number of dimensions from the input vector; so the matrix function might transform the vector from one space to another - or in notation, ${\\rm I\\!R}$<sup>n</sup> -> ${\\rm I\\!R}$<sup>m</sup>.\n",
    "\n",
    "For example, let's redefine matrix ***A***, while retaining our original definition of vector ***v***:\n",
    "\n",
    "$$ A = \\begin{bmatrix}2 & 3\\\\5 & 2\\\\1 & 1\\end{bmatrix} \\;\\;\\;\\; \\vec{v} = \\begin{bmatrix}1\\\\2\\end{bmatrix}$$\n",
    "\n",
    "Now if we once again define ***T*** like this:\n",
    "\n",
    "$$ T(\\vec{v}) = A\\vec{v} $$\n",
    "\n",
    "We apply the transformation like this:\n",
    "\n",
    "$$\\begin{bmatrix}2 & 3\\\\5 & 2\\\\1 & 1\\end{bmatrix} \\cdot  \\begin{bmatrix}1\\\\2\\end{bmatrix} = \\begin{bmatrix}8\\\\9\\\\3\\end{bmatrix}$$\n",
    "\n",
    "So now, our transformation transforms the vector from 2-dimensional space to 3-dimensional space:\n",
    "\n",
    "$$ T: \\rm I\\!R^{2} \\to \\rm I\\!R^{3} $$\n",
    "\n",
    "Here it is in Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "v = np.array([1,2])\n",
    "A = np.array([[2,3],\n",
    "              [5,2],\n",
    "              [1,1]])\n",
    "\n",
    "t = A@v\n",
    "print (t)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "v = np.array([1,2])\n",
    "A = np.array([[1,2],\n",
    "              [2,1]])\n",
    "\n",
    "t = A@v\n",
    "print (t)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Transformations of Magnitude and Amplitude\n",
    "\n",
    "When you multiply a vector by a matrix, you transform it in at least one of the following two ways:\n",
    "* Scale the length (*magnitude*) of the matrix to make it longer or shorter\n",
    "* Change the direction (*amplitude*) of the matrix\n",
    "\n",
    "For example consider the following matrix and vector:\n",
    "\n",
    "$$ A = \\begin{bmatrix}2 & 0\\\\0 & 2\\end{bmatrix} \\;\\;\\;\\; \\vec{v} = \\begin{bmatrix}1\\\\0\\end{bmatrix}$$\n",
    "\n",
    "As before, we transform the vector ***v*** by multiplying it with the matrix ***A***:\n",
    "\n",
    "\\begin{equation}\\begin{bmatrix}2 & 0\\\\0 & 2\\end{bmatrix} \\cdot  \\begin{bmatrix}1\\\\0\\end{bmatrix} = \\begin{bmatrix}2\\\\0\\end{bmatrix}\\end{equation}\n",
    "\n",
    "In this case, the resulting vector has changed in length (*magnitude*), but has not changed its direction (*amplitude*).\n",
    "\n",
    "Let's visualize that in Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "v = np.array([1,0])\n",
    "A = np.array([[2,0],\n",
    "              [0,2]])\n",
    "\n",
    "t = A@v\n",
    "print (t)\n",
    "\n",
    "# Plot v and t\n",
    "vecs = np.array([t,v])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['blue', 'orange'], scale=10)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The original vector ***v*** is shown in orange, and the transformed vector ***t*** is shown in blue - note that ***t*** has the same direction (*amplitude*) as ***v*** but a greater length (*magnitude*).\n",
    "\n",
    "Now let's use a different matrix to transform the vector ***v***:\n",
    "\\begin{equation}\\begin{bmatrix}0 & -1\\\\1 & 0\\end{bmatrix} \\cdot  \\begin{bmatrix}1\\\\0\\end{bmatrix} = \\begin{bmatrix}0\\\\1\\end{bmatrix}\\end{equation}\n",
    "\n",
    "This time, the resulting vector has been changed to a different amplitude, but has the same magnitude."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "v = np.array([1,0])\n",
    "A = np.array([[0,-1],\n",
    "              [1,0]])\n",
    "\n",
    "t = A@v\n",
    "print (t)\n",
    "\n",
    "# Plot v and t\n",
    "vecs = np.array([v,t])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['orange', 'blue'], scale=10)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's see change the matrix one more time:\n",
    "\\begin{equation}\\begin{bmatrix}2 & 1\\\\1 & 2\\end{bmatrix} \\cdot  \\begin{bmatrix}1\\\\0\\end{bmatrix} = \\begin{bmatrix}2\\\\1\\end{bmatrix}\\end{equation}\n",
    "\n",
    "Now our resulting vector has been transformed to a new amplitude *and* magnitude - the transformation has affected both direction and scale."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "v = np.array([1,0])\n",
    "A = np.array([[2,1],\n",
    "              [1,2]])\n",
    "\n",
    "t = A@v\n",
    "print (t)\n",
    "\n",
    "# Plot v and t\n",
    "vecs = np.array([v,t])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['orange', 'blue'], scale=10)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Afine Transformations\n",
    "An Afine transformation multiplies a vector by a matrix and adds an offset vector, sometimes referred to as *bias*; like this:\n",
    "\n",
    "$$T(\\vec{v}) = A\\vec{v} + \\vec{b}$$\n",
    "\n",
    "For example:\n",
    "\n",
    "\\begin{equation}\\begin{bmatrix}5 & 2\\\\3 & 1\\end{bmatrix} \\cdot  \\begin{bmatrix}1\\\\1\\end{bmatrix} + \\begin{bmatrix}-2\\\\-6\\end{bmatrix} = \\begin{bmatrix}5\\\\-2\\end{bmatrix}\\end{equation}\n",
    "\n",
    "This kind of transformation is actually the basis of linear regression, which is a core foundation for machine learning. The matrix defines the *features*, the first vector is the *coefficients*, and the bias vector is the *intercept*.\n",
    "\n",
    "here's an example of an Afine transformation in Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "v = np.array([1,1])\n",
    "A = np.array([[5,2],\n",
    "              [3,1]])\n",
    "b = np.array([-2,-6])\n",
    "\n",
    "t = A@v + b\n",
    "print (t)\n",
    "\n",
    "# Plot v and t\n",
    "vecs = np.array([v,t])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['orange', 'blue'], scale=15)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Eigenvectors and Eigenvalues\n",
    "So we can see that when you transform a vector using a matrix, we change its direction, length, or both. When the transformation only affects scale (in other words, the output vector has a different magnitude but the same amplitude as the input vector), the matrix multiplication for the transformation is the equivalent operation as some scalar multiplication of the vector.\n",
    "\n",
    "For example, earlier we examined the following transformation that dot-mulitplies a vector by a matrix:\n",
    "\n",
    "$$\\begin{bmatrix}2 & 0\\\\0 & 2\\end{bmatrix} \\cdot  \\begin{bmatrix}1\\\\0\\end{bmatrix} = \\begin{bmatrix}2\\\\0\\end{bmatrix}$$\n",
    "\n",
    "You can achieve the same result by mulitplying the vector by the scalar value ***2***:\n",
    "\n",
    "$$2 \\times \\begin{bmatrix}1\\\\0\\end{bmatrix} = \\begin{bmatrix}2\\\\0\\end{bmatrix}$$\n",
    "\n",
    "The following python performs both of these calculation and shows the results, which are identical."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "v = np.array([1,0])\n",
    "A = np.array([[2,0],\n",
    "              [0,2]])\n",
    "\n",
    "t1 = A@v\n",
    "print (t1)\n",
    "t2 = 2*v\n",
    "print (t2)\n",
    "\n",
    "fig = plt.figure()\n",
    "a=fig.add_subplot(1,1,1)\n",
    "# Plot v and t1\n",
    "vecs = np.array([t1,v])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['blue', 'orange'], scale=10)\n",
    "plt.show()\n",
    "a=fig.add_subplot(1,2,1)\n",
    "# Plot v and t2\n",
    "vecs = np.array([t2,v])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['blue', 'orange'], scale=10)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In cases like these, where a matrix transformation is the equivelent of a scalar-vector multiplication, the scalar-vector pairs that correspond to the matrix are known respectively as eigenvalues and eigenvectors. We generally indicate eigenvalues using the Greek letter lambda (&lambda;), and the formula that defines eigenvalues and eigenvectors with respect to a transformation is:\n",
    "\n",
    "$$ T(\\vec{v}) = \\lambda\\vec{v}$$\n",
    "\n",
    "Where the vector ***v*** is an eigenvector and the value ***&lambda;*** is an eigenvalue for transformation ***T***.\n",
    "\n",
    "When the transformation ***T*** is represented as a matrix multiplication, as in this case where the transformation is represented by matrix ***A***:\n",
    "\n",
    "$$ T(\\vec{v}) = A\\vec{v} = \\lambda\\vec{v}$$\n",
    "\n",
    "Then  ***v*** is an eigenvector and ***&lambda;*** is an eigenvalue of ***A***.\n",
    "\n",
    "A matrix can have multiple eigenvector-eigenvalue pairs, and you can calculate them manually. However, it's generally easier to use a tool or programming language. For example, in Python you can use the ***linalg.eig*** function, which returns an array of eigenvalues and a matrix of the corresponding eigenvectors for the specified matrix.\n",
    "\n",
    "Here's an example that returns the eigenvalue and eigenvector pairs for the following matrix:\n",
    "\n",
    "$$A=\\begin{bmatrix}2 & 0\\\\0 & 3\\end{bmatrix}$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "A = np.array([[2,0],\n",
    "              [0,3]])\n",
    "eVals, eVecs = np.linalg.eig(A)\n",
    "print(eVals)\n",
    "print(eVecs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So there are two eigenvalue-eigenvector pairs for this matrix, as shown here:\n",
    "\n",
    "$$ \\lambda_{1} = 2, \\vec{v_{1}} = \\begin{bmatrix}1 \\\\ 0\\end{bmatrix}  \\;\\;\\;\\;\\;\\; \\lambda_{2} = 3, \\vec{v_{2}} = \\begin{bmatrix}0 \\\\ 1\\end{bmatrix} $$\n",
    "\n",
    "Let's verify that multiplying each eigenvalue-eigenvector pair corresponds to the dot-product of the eigenvector and the matrix. Here's the first pair:\n",
    "\n",
    "$$ 2 \\times \\begin{bmatrix}1 \\\\ 0\\end{bmatrix} = \\begin{bmatrix}2 \\\\ 0\\end{bmatrix}  \\;\\;\\;and\\;\\;\\; \\begin{bmatrix}2 & 0\\\\0 & 3\\end{bmatrix} \\cdot \\begin{bmatrix}1 \\\\ 0\\end{bmatrix} = \\begin{bmatrix}2 \\\\ 0\\end{bmatrix} $$\n",
    "\n",
    "So far so good. Now let's check the second pair:\n",
    "\n",
    "$$ 3 \\times \\begin{bmatrix}0 \\\\ 1\\end{bmatrix} = \\begin{bmatrix}0 \\\\ 3\\end{bmatrix}  \\;\\;\\;and\\;\\;\\; \\begin{bmatrix}2 & 0\\\\0 & 3\\end{bmatrix} \\cdot \\begin{bmatrix}0 \\\\ 1\\end{bmatrix} = \\begin{bmatrix}0 \\\\ 3\\end{bmatrix} $$\n",
    "\n",
    "So our eigenvalue-eigenvector scalar multiplications do indeed correspond to our matrix-eigenvector dot-product transformations.\n",
    "\n",
    "Here's the equivalent code in Python, using the ***eVals*** and ***eVecs*** variables you generated in the previous code cell:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vec1 = eVecs[:,0]\n",
    "lam1 = eVals[0]\n",
    "\n",
    "print('Matrix A:')\n",
    "print(A)\n",
    "print('-------')\n",
    "\n",
    "print('lam1: ' + str(lam1))\n",
    "print ('v1: ' + str(vec1))\n",
    "print ('Av1: ' + str(A@vec1))\n",
    "print ('lam1 x v1: ' + str(lam1*vec1))\n",
    "\n",
    "print('-------')\n",
    "\n",
    "vec2 = eVecs[:,1]\n",
    "lam2 = eVals[1]\n",
    "\n",
    "print('lam2: ' + str(lam2))\n",
    "print ('v2: ' + str(vec2))\n",
    "print ('Av2: ' + str(A@vec2))\n",
    "print ('lam2 x v2: ' + str(lam2*vec2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can use the following code to visualize these transformations:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "t1 = lam1*vec1\n",
    "print (t1)\n",
    "t2 = lam2*vec2\n",
    "print (t2)\n",
    "\n",
    "fig = plt.figure()\n",
    "a=fig.add_subplot(1,1,1)\n",
    "# Plot v and t1\n",
    "vecs = np.array([t1,vec1])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['blue', 'orange'], scale=10)\n",
    "plt.show()\n",
    "a=fig.add_subplot(1,2,1)\n",
    "# Plot v and t2\n",
    "vecs = np.array([t2,vec2])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['blue', 'orange'], scale=10)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Similarly, earlier we examined the following matrix transformation:\n",
    "\n",
    "$$\\begin{bmatrix}2 & 0\\\\0 & 2\\end{bmatrix} \\cdot  \\begin{bmatrix}1\\\\0\\end{bmatrix} = \\begin{bmatrix}2\\\\0\\end{bmatrix}$$\n",
    "\n",
    "And we saw that you can achieve the same result by mulitplying the vector by the scalar value ***2***:\n",
    "\n",
    "$$2 \\times \\begin{bmatrix}1\\\\0\\end{bmatrix} = \\begin{bmatrix}2\\\\0\\end{bmatrix}$$\n",
    "\n",
    "This works because the scalar value 2 and the vector (1,0) are an eigenvalue-eigenvector pair for this matrix.\n",
    "\n",
    "Let's use Python to determine the eigenvalue-eigenvector pairs for this matrix:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "A = np.array([[2,0],\n",
    "              [0,2]])\n",
    "eVals, eVecs = np.linalg.eig(A)\n",
    "print(eVals)\n",
    "print(eVecs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So once again, there are two eigenvalue-eigenvector pairs for this matrix, as shown here:\n",
    "\n",
    "$$ \\lambda_{1} = 2, \\vec{v_{1}} = \\begin{bmatrix}1 \\\\ 0\\end{bmatrix}  \\;\\;\\;\\;\\;\\; \\lambda_{2} = 2, \\vec{v_{2}} = \\begin{bmatrix}0 \\\\ 1\\end{bmatrix} $$\n",
    "\n",
    "Let's verify that multiplying each eigenvalue-eigenvector pair corresponds to the dot-product of the eigenvector and the matrix. Here's the first pair:\n",
    "\n",
    "$$ 2 \\times \\begin{bmatrix}1 \\\\ 0\\end{bmatrix} = \\begin{bmatrix}2 \\\\ 0\\end{bmatrix}  \\;\\;\\;and\\;\\;\\; \\begin{bmatrix}2 & 0\\\\0 & 2\\end{bmatrix} \\cdot \\begin{bmatrix}1 \\\\ 0\\end{bmatrix} = \\begin{bmatrix}2 \\\\ 0\\end{bmatrix} $$\n",
    "\n",
    "Well, we already knew that. Now let's check the second pair:\n",
    "\n",
    "$$ 2 \\times \\begin{bmatrix}0 \\\\ 1\\end{bmatrix} = \\begin{bmatrix}0 \\\\ 2\\end{bmatrix}  \\;\\;\\;and\\;\\;\\; \\begin{bmatrix}2 & 0\\\\0 & 2\\end{bmatrix} \\cdot \\begin{bmatrix}0 \\\\ 1\\end{bmatrix} = \\begin{bmatrix}0 \\\\ 2\\end{bmatrix} $$\n",
    "\n",
    "Now let's use Pythonto verify and plot these transformations:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vec1 = eVecs[:,0]\n",
    "lam1 = eVals[0]\n",
    "\n",
    "print('Matrix A:')\n",
    "print(A)\n",
    "print('-------')\n",
    "\n",
    "print('lam1: ' + str(lam1))\n",
    "print ('v1: ' + str(vec1))\n",
    "print ('Av1: ' + str(A@vec1))\n",
    "print ('lam1 x v1: ' + str(lam1*vec1))\n",
    "\n",
    "print('-------')\n",
    "\n",
    "vec2 = eVecs[:,1]\n",
    "lam2 = eVals[1]\n",
    "\n",
    "print('lam2: ' + str(lam2))\n",
    "print ('v2: ' + str(vec2))\n",
    "print ('Av2: ' + str(A@vec2))\n",
    "print ('lam2 x v2: ' + str(lam2*vec2))\n",
    "\n",
    "\n",
    "# Plot the resulting vectors\n",
    "t1 = lam1*vec1\n",
    "t2 = lam2*vec2\n",
    "\n",
    "fig = plt.figure()\n",
    "a=fig.add_subplot(1,1,1)\n",
    "# Plot v and t1\n",
    "vecs = np.array([t1,vec1])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['blue', 'orange'], scale=10)\n",
    "plt.show()\n",
    "a=fig.add_subplot(1,2,1)\n",
    "# Plot v and t2\n",
    "vecs = np.array([t2,vec2])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['blue', 'orange'], scale=10)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's take a look at one more, slightly more complex example. Here's our matrix:\n",
    "\n",
    "$$\\begin{bmatrix}2 & 1\\\\1 & 2\\end{bmatrix}$$\n",
    "\n",
    "Let's get the eigenvalue and eigenvector pairs:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "A = np.array([[2,1],\n",
    "              [1,2]])\n",
    "\n",
    "eVals, eVecs = np.linalg.eig(A)\n",
    "print(eVals)\n",
    "print(eVecs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This time the eigenvalue-eigenvector pairs are:\n",
    "\n",
    "$$ \\lambda_{1} = 3, \\vec{v_{1}} = \\begin{bmatrix}0.70710678 \\\\ 0.70710678\\end{bmatrix}  \\;\\;\\;\\;\\;\\; \\lambda_{2} = 1, \\vec{v_{2}} = \\begin{bmatrix}-0.70710678 \\\\ 0.70710678\\end{bmatrix} $$\n",
    "\n",
    "So let's check the first pair:\n",
    "\n",
    "$$ 3 \\times \\begin{bmatrix}0.70710678 \\\\ 0.70710678\\end{bmatrix} = \\begin{bmatrix}2.12132034 \\\\ 2.12132034\\end{bmatrix}  \\;\\;\\;and\\;\\;\\; \\begin{bmatrix}2 & 1\\\\0 & 2\\end{bmatrix} \\cdot \\begin{bmatrix}0.70710678 \\\\ 0.70710678\\end{bmatrix} = \\begin{bmatrix}2.12132034 \\\\ 2.12132034\\end{bmatrix} $$\n",
    "\n",
    "Now let's check the second pair:\n",
    "\n",
    "$$ 1 \\times \\begin{bmatrix}-0.70710678 \\\\ 0.70710678\\end{bmatrix} = \\begin{bmatrix}-0.70710678\\\\0.70710678\\end{bmatrix}  \\;\\;\\;and\\;\\;\\; \\begin{bmatrix}2 & 1\\\\1 & 2\\end{bmatrix} \\cdot \\begin{bmatrix}-0.70710678 \\\\ 0.70710678\\end{bmatrix} = \\begin{bmatrix}-0.70710678\\\\0.70710678\\end{bmatrix} $$\n",
    "\n",
    "With more complex examples like this, it's generally easier to do it with Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vec1 = eVecs[:,0]\n",
    "lam1 = eVals[0]\n",
    "\n",
    "print('Matrix A:')\n",
    "print(A)\n",
    "print('-------')\n",
    "\n",
    "print('lam1: ' + str(lam1))\n",
    "print ('v1: ' + str(vec1))\n",
    "print ('Av1: ' + str(A@vec1))\n",
    "print ('lam1 x v1: ' + str(lam1*vec1))\n",
    "\n",
    "print('-------')\n",
    "\n",
    "vec2 = eVecs[:,1]\n",
    "lam2 = eVals[1]\n",
    "\n",
    "print('lam2: ' + str(lam2))\n",
    "print ('v2: ' + str(vec2))\n",
    "print ('Av2: ' + str(A@vec2))\n",
    "print ('lam2 x v2: ' + str(lam2*vec2))\n",
    "\n",
    "\n",
    "# Plot the results\n",
    "t1 = lam1*vec1\n",
    "t2 = lam2*vec2\n",
    "\n",
    "fig = plt.figure()\n",
    "a=fig.add_subplot(1,1,1)\n",
    "# Plot v and t1\n",
    "vecs = np.array([t1,vec1])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['blue', 'orange'], scale=10)\n",
    "plt.show()\n",
    "a=fig.add_subplot(1,2,1)\n",
    "# Plot v and t2\n",
    "vecs = np.array([t2,vec2])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['blue', 'orange'], scale=10)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Eigendecomposition\n",
    "So we've learned a little about eigenvalues and eigenvectors; but you may be wondering what use they are. Well, one use for them is to help decompose transformation matrices.\n",
    "\n",
    "Recall that previously we found that a matrix transformation of a vector changes its magnitude, amplitude, or both. Without getting too technical about it, we need to remember that vectors can exist in any spatial orientation, or *basis*; and the same transformation can be applied in different *bases*.\n",
    "\n",
    "We can decompose a matrix using the following formula:\n",
    "\n",
    "$$A = Q \\Lambda Q^{-1}$$\n",
    "\n",
    "Where ***A*** is a trasformation that can be applied to a vector in its current base, ***Q*** is a matrix of eigenvectors that defines a change of basis, and ***&Lambda;*** is a matrix with eigenvalues on the diagonal that defines the same linear transformation as ***A*** in the base defined by ***Q***.\n",
    "\n",
    "Let's look at these in some more detail. Consider this matrix:\n",
    "\n",
    "$$A=\\begin{bmatrix}3 & 2\\\\1 & 0\\end{bmatrix}$$\n",
    "\n",
    "***Q*** is a matrix in which each column is an eigenvector of ***A***; which as we've seen previously, we can calculate using Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "A = np.array([[3,2],\n",
    "              [1,0]])\n",
    "\n",
    "l, Q = np.linalg.eig(A)\n",
    "print(Q)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So for matrix ***A***, ***Q*** is the following matrix:\n",
    "\n",
    "$$Q=\\begin{bmatrix}0.96276969 & -0.48963374\\\\0.27032301 & 0.87192821\\end{bmatrix}$$\n",
    "\n",
    "***&Lambda;*** is a matrix that contains the eigenvalues for ***A*** on the diagonal, with zeros in all other elements; so for a 2x2 matrix, &Lambda; will look like this:\n",
    "\n",
    "$$\\Lambda=\\begin{bmatrix}\\lambda_{1} & 0\\\\0 & \\lambda_{2}\\end{bmatrix}$$\n",
    "\n",
    "In our Python code, we've already used the ***linalg.eig*** function to return the array of eigenvalues for ***A*** into the variable ***l***, so now we just need to format that as a matrix:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "L = np.diag(l)\n",
    "print (L)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So ***&Lambda;*** is the following matrix:\n",
    "\n",
    "$$\\Lambda=\\begin{bmatrix}3.56155281 & 0\\\\0 & -0.56155281\\end{bmatrix}$$\n",
    "\n",
    "Now we just need to find ***Q<sup>-1</sup>***, which is the inverse of ***Q***:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Qinv = np.linalg.inv(Q)\n",
    "print(Qinv)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The inverse of ***Q*** then, is:\n",
    "\n",
    "$$Q^{-1}=\\begin{bmatrix}0.89720673 & 0.50382896\\\\-0.27816009 & 0.99068183\\end{bmatrix}$$\n",
    "\n",
    "So what does that mean? Well, it means that we can decompose the transformation of *any* vector multiplied by matrix ***A*** into the separate operations ***Q&Lambda;Q<sup>-1</sup>***:\n",
    "\n",
    "$$A\\vec{v} = Q \\Lambda Q^{-1}\\vec{v}$$\n",
    "\n",
    "To prove this, let's take vector ***v***:\n",
    "\n",
    "$$\\vec{v} = \\begin{bmatrix}1\\\\3\\end{bmatrix} $$\n",
    "\n",
    "Our matrix transformation using ***A*** is:\n",
    "\n",
    "$$\\begin{bmatrix}3 & 2\\\\1 & 0\\end{bmatrix} \\cdot \\begin{bmatrix}1\\\\3\\end{bmatrix} $$\n",
    "\n",
    "So let's show the results of that using Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v = np.array([1,3])\n",
    "t = A@v\n",
    "\n",
    "print(t)\n",
    "\n",
    "# Plot v and t\n",
    "vecs = np.array([v,t])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['orange', 'b'], scale=20)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now, let's do the same thing using the ***Q&Lambda;Q<sup>-1</sup>*** sequence of operations:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import math\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "t = (Q@(L@(Qinv)))@v\n",
    "\n",
    "# Plot v and t\n",
    "vecs = np.array([v,t])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['orange', 'b'], scale=20)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So ***A*** and ***Q&Lambda;Q<sup>-1</sup>*** are equivalent.\n",
    "\n",
    "If we view the intermediary stages of the decomposed transformation, you can see the transformation using ***A*** in the original base for ***v*** (orange to blue) and the transformation using ***&Lambda;*** in the change of basis decribed by ***Q*** (red to magenta):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "t1 = Qinv@v\n",
    "t2 = L@t1\n",
    "t3 = Q@t2\n",
    "\n",
    "# Plot the transformations\n",
    "vecs = np.array([v,t1, t2, t3])\n",
    "origin = [0], [0]\n",
    "plt.axis('equal')\n",
    "plt.grid()\n",
    "plt.ticklabel_format(style='sci', axis='both', scilimits=(0,0))\n",
    "plt.quiver(*origin, vecs[:,0], vecs[:,1], color=['orange', 'red', 'magenta', 'blue'], scale=20)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So from this visualization, it should be apparent that the transformation ***Av*** can be performed by changing the basis for ***v*** using ***Q*** (from orange to red in the above plot) applying the equivalent linear transformation in that base using ***&Lambda;*** (red to magenta), and switching back to the original base using ***Q<sup>-1</sup>*** (magenta to blue)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Rank of a Matrix\n",
    "\n",
    "The **rank** of a square matrix is the number of non-zero eigenvalues of the matrix. A **full rank** matrix has the same number of non-zero eigenvalues as the dimension of the matrix. A **rank-deficient** matrix has fewer non-zero eigenvalues as dimensions. The inverse of a rank deficient matrix is singular and so does not exist (this is why in a previous notebook we noted that some matrices have no inverse).\n",
    "\n",
    "Consider the following matrix ***A***:\n",
    "\n",
    "$$A=\\begin{bmatrix}1 & 2\\\\4 & 3\\end{bmatrix}$$\n",
    "\n",
    "Let's find its eigenvalues (***&Lambda;***):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "A = np.array([[1,2],\n",
    "              [4,3]])\n",
    "l, Q = np.linalg.eig(A)\n",
    "L = np.diag(l)\n",
    "print(L)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\\Lambda=\\begin{bmatrix}-1 & 0\\\\0 & 5\\end{bmatrix}$$\n",
    "\n",
    "This matrix has full rank. The dimensions of the matrix is 2. There are two non-zero eigenvalues. \n",
    "\n",
    "Now consider this matrix:\n",
    "\n",
    "$$B=\\begin{bmatrix}3 & -3 & 6\\\\2 & -2 & 4\\\\1 & -1 & 2\\end{bmatrix}$$\n",
    "\n",
    "Note that the second and third columns are just scalar multiples of the first column.\n",
    "\n",
    "Let's examine it's eigenvalues:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "B = np.array([[3,-3,6],\n",
    "              [2,-2,4],\n",
    "              [1,-1,2]])\n",
    "lb, Qb = np.linalg.eig(B)\n",
    "Lb = np.diag(lb)\n",
    "print(Lb)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\\Lambda=\\begin{bmatrix}3 & 0& 0\\\\0 & -6\\times10^{-17} & 0\\\\0 & 0 & 3.6\\times10^{-16}\\end{bmatrix}$$\n",
    "\n",
    "Note that matrix has only 1 non-zero eigenvalue. The other two eigenvalues are so extremely small as to be effectively zero. This is an example of a rank-deficient matrix; and as such, it has no inverse."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Inverse of a Square Full Rank Matrix\n",
    "You can calculate the inverse of a square full rank matrix by using the following formula:\n",
    "\n",
    "$$A^{-1} = Q \\Lambda^{-1} Q^{-1}$$\n",
    "\n",
    "Let's apply this to matrix ***A***:\n",
    "\n",
    "$$A=\\begin{bmatrix}1 & 2\\\\4 & 3\\end{bmatrix}$$\n",
    "\n",
    "Let's find the matrices for ***Q***, ***&Lambda;<sup>-1</sup>***, and ***Q<sup>-1</sup>***:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "A = np.array([[1,2],\n",
    "              [4,3]])\n",
    "\n",
    "l, Q = np.linalg.eig(A)\n",
    "L = np.diag(l)\n",
    "print(Q)\n",
    "Linv = np.linalg.inv(L)\n",
    "Qinv = np.linalg.inv(Q)\n",
    "print(Linv)\n",
    "print(Qinv)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So:\n",
    "\n",
    "$$A^{-1}=\\begin{bmatrix}-0.70710678 & -0.4472136\\\\0.70710678 & -0.89442719\\end{bmatrix}\\cdot\\begin{bmatrix}-1 & -0\\\\0 & 0.2\\end{bmatrix}\\cdot\\begin{bmatrix}-0.94280904 & 0.47140452\\\\-0.74535599 & -0.74535599\\end{bmatrix}$$\n",
    "\n",
    "Let's calculate that in Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Ainv = (Q@(Linv@(Qinv)))\n",
    "print(Ainv)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That gives us the result:\n",
    "\n",
    "$$A^{-1}=\\begin{bmatrix}-0.6 & 0.4\\\\0.8 & -0.2\\end{bmatrix}$$\n",
    "\n",
    "We can apply the ***np.linalg.inv*** function directly to ***A*** to verify this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(np.linalg.inv(A))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.6",
   "language": "python",
   "name": "python36"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
